package eCheque;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.Key;
import java.security.PrivateKey;
import java.util.Calendar;

import javax.crypto.Cipher;

import com.sun.org.apache.xml.internal.security.signature.InvalidSignatureValueException;

/**
 * @author Steve Pennington
 * 
 */
public class ChequeReceiver extends Thread {

	private final String SLASH = System.getProperty("file.separator");
	
	public static int PORT = 8190;
	private static int MAX_FAILS = 5;

	private int failCount = 0;
	private DigitalCertificate revieverCertificate;
	private PrivateKey privateKey;
	private EChequeRegisteration userAccount;
	private ReceiveChequeHandler handler;

	public ChequeReceiver(EChequeRegisteration userAccount,
			DigitalCertificate revieverCertificate, PrivateKey privateKey, ReceiveChequeHandler handler) {
		this.revieverCertificate = revieverCertificate;
		this.privateKey = privateKey;
		this.userAccount = userAccount;
		this.handler = handler;
	}

	public void run() {
		ServerSocket inputSocket = null;

		for (;;) {
			//Try to open the socket MAX_FAILS times. If we can't notify the
			//callback and commit kill ourselves
			if(inputSocket == null || inputSocket.isClosed()) {
				while(failCount < MAX_FAILS) {
					try {
						inputSocket = new ServerSocket(PORT);
						break;
					} catch (IOException e) {
						if(++failCount >= MAX_FAILS) {
							handler.startupFailed(e);
							return;
						}
					}
				}
			}
				
			try {
				handleConnection(inputSocket.accept());
			} catch(IOException e) {
				handler.connectFailed(e);
				try {
					inputSocket.close();
				} catch(IOException e2) {}
			}
		}
	}

	private synchronized void handleConnection(Socket socket) {
		ObjectOutputStream outputStream = null;
		ObjectInputStream inputStream = null;
		FileOutputStream chequeOutputStream = null;
		FileInputStream chequeInputStream = null;
		
		try {
			outputStream = new ObjectOutputStream(
					socket.getOutputStream());
			inputStream = new ObjectInputStream(
					socket.getInputStream());

			// Exchange digital certificates
			DigitalCertificate senderCertificate = (DigitalCertificate) inputStream
					.readObject();
			outputStream.writeObject(revieverCertificate);
			outputStream.flush();

			int keyLength = inputStream.readInt();
			// TODO: check that keyLength isnt crazy
			byte[] wrappedKey = new byte[keyLength];
			inputStream.read(wrappedKey);
			// TODO: check read = keyLength
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.UNWRAP_MODE, this.privateKey);
			Key sessionKey = cipher
					.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY);

			Calendar now = Calendar.getInstance();

			StringBuffer chequeNameBuffer = new StringBuffer();
			chequeNameBuffer.append(now.get(Calendar.YEAR));
			chequeNameBuffer.append(now.get(Calendar.MONTH));
			chequeNameBuffer.append(now.get(Calendar.DAY_OF_MONTH));
			chequeNameBuffer.append(now.get(Calendar.HOUR));
			chequeNameBuffer.append(now.get(Calendar.MILLISECOND));
			String chequeName = chequeNameBuffer.toString();

			chequeOutputStream = new FileOutputStream(
					userAccount.getEWalletLoaction() + SLASH + "Incoming" + SLASH + chequeName + ".cry");
			byte[] buffer = new byte[1024];
			int numread;
			while ((numread = inputStream.read(buffer)) >= 0) {
				chequeOutputStream.write(buffer, 0, numread);
			}
			EChequeUtils.safeClose(chequeOutputStream);

			// validate the received cheque.
			chequeInputStream = new FileInputStream(
					userAccount.getEWalletLoaction() + SLASH + "Incoming" + SLASH + chequeName + ".cry");
			chequeOutputStream = new FileOutputStream(
					userAccount.getEWalletLoaction() + SLASH + "My Cheques" + SLASH + chequeName + ".sec");

			// create AES object to decrypt the received cheque
			AESCrypt aesObj = new AESCrypt();
			Cipher aesCipher = aesObj.initializeCipher(sessionKey, 1);
			aesObj.crypt(chequeInputStream, chequeOutputStream, aesCipher);
			EChequeUtils.safeClose(chequeInputStream);
			EChequeUtils.safeClose(chequeOutputStream);

			// verify the cheque siganture using the sender public key.
			Digitalsigneture digitalSign = new Digitalsigneture();

			// load decrypted chequeObject.
			EChequeIO readChq = new EChequeIO();
			ECheque recivedChq = new ECheque();
			recivedChq = readChq.readcheque(userAccount.getEWalletLoaction() + SLASH + "My Cheques" + SLASH + chequeName + ".sec");
			String chqSign = ChequeReferenceString(recivedChq);

			boolean verifySign = digitalSign.verifySignature(
					recivedChq.getdrawersiganure(), chqSign,
					senderCertificate.getpublicKey());
			
			if (verifySign) {
				handler.chequeReceived(recivedChq);
			} else {
				throw new InvalidSignatureValueException();
			}
		} catch (Exception e) {
			handler.connectFailed(e);
		} finally {
			EChequeUtils.safeClose(chequeInputStream);
			EChequeUtils.safeClose(chequeOutputStream);
			EChequeUtils.safeClose(inputStream);
			EChequeUtils.safeClose(outputStream);
		}
	}

	private String ChequeReferenceString(ECheque chq) {

		String ref = "";
		ref += chq.getaccountNumber() + chq.getaccountholder()
				+ chq.getbankname() + chq.getchequeNumber() + chq.getMoney()
				+ chq.getcurrencytype() + chq.getearnday()
				+ chq.getguaranteed() + chq.getpayToOrderOf();

		return ref;
	}
	
	public synchronized void setRevieverCertificate(DigitalCertificate revieverCertificate) {
		this.revieverCertificate = revieverCertificate;
	}

	public synchronized void setPrivateKey(PrivateKey privateKey) {
		this.privateKey = privateKey;
	}

	public synchronized void setUserAccount(EChequeRegisteration userAccount) {
		this.userAccount = userAccount;
	}
}
